#include "AppUtilPrecompiled.hxx"
#include "AppUtilReaderWriter.hxx"
using namespace Utility;

BasicReaderWriter::BasicReaderWriter()
{
#ifdef REIGN_DESKTOP
	//String16 wszPath = L""; 
	//wszPath.append( L"Assets\\" );
	//auto Status = CreateDirectoryW( 
	//	wszPath.data(), nullptr 
	//	);
	//m_LocationPath = wszPath;
	m_LocationPath = L"";
#else
    m_Folder = Windows::ApplicationModel::Package::Current->InstalledLocation;
    m_Path = Platform::String::Concat(m_Folder->Path, "\\");
#endif
}

BasicReaderWriter::BasicReaderWriter( 
	_In_ BasicReaderWriter::FolderArg Folder
	) 
{
#ifdef REIGN_DESKTOP
	m_LocationPath = Folder; // Copy path
	if (m_LocationPath[m_LocationPath.size() - 1] != L'\\')
		m_LocationPath += L"\\"; // Add separator if needed
#else
	m_Folder = Folder;
	auto lPath = m_Folder->Path;
	if ( !lPath->Length() )
	{
        // Applications are not permitted to access certain folders, such as the Documents folder, using this
        // code path. In such cases, the Path property for the folder will be an empty string.
        throw ref new Platform::FailureException();
	}
	else
	{
		m_Path = Platform::String::Concat(lPath, "\\");
	}
#endif
}

BasicReaderWriter::ByteArrayPtr BasicReaderWriter::ReadData( 
	_In_ BasicReaderWriter::StringArg FileName 
	)
{
	CREATEFILE2_EXTENDED_PARAMETERS lCreateFileParams = { 0 };
	lCreateFileParams.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS);
	lCreateFileParams.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
	lCreateFileParams.dwFileFlags = FILE_FLAG_SEQUENTIAL_SCAN;
	lCreateFileParams.dwSecurityQosFlags = SECURITY_ANONYMOUS;
	lCreateFileParams.lpSecurityAttributes = nullptr;
	lCreateFileParams.hTemplateFile = nullptr;
	
#ifdef REIGN_DESKTOP
	auto TempPathStr = m_LocationPath + FileName;
	auto Path = TempPathStr.data();
#else
	auto TempPathStr = Platform::String::Concat(m_Path, FileName);
	auto Path = TempPathStr->Data();
#endif

	WRL::Wrappers::FileHandle lFile(
		CreateFile2(
			Path,
			GENERIC_READ, // !
			FILE_SHARE_READ,
			OPEN_EXISTING, // !
			&lCreateFileParams
			)
		);

	if (lFile.Get() == INVALID_HANDLE_VALUE)
	{
#ifdef REIGN_DESKTOP
		throw std::exception();
#else
		throw ref new Platform::FailureException();
#endif
	}

	FILE_STANDARD_INFO lFileInfo = { 0 };
	if (!GetFileInformationByHandleEx(
		lFile.Get(),
		FileStandardInfo,
		&lFileInfo,
		sizeof(lFileInfo)
		))
	{
#ifdef REIGN_DESKTOP
		throw std::exception();
#else
		throw ref new Platform::FailureException();
#endif
	}

	if (lFileInfo.EndOfFile.HighPart != 0)
	{
#ifdef REIGN_DESKTOP
		throw std::exception();
#else
		throw ref new Platform::FailureException();
#endif
	}

#ifdef REIGN_DESKTOP
	ByteArrayPtr FileData( new Utility::Array<Byte>(lFileInfo.EndOfFile.LowPart, nullptr) );
#else
	ByteArrayPtr FileData = ref new Platform::Array<Byte>(lFileInfo.EndOfFile.LowPart);
#endif

	if (!ReadFile(
		lFile.Get(),
		FileData->Data,
		FileData->Length,
		nullptr,
		nullptr
		))
	{
#ifdef REIGN_DESKTOP
		throw std::exception();
#else
		throw ref new Platform::FailureException();
#endif
	}

	return( FileData );
}

BasicReaderWriter::AsyncByteArrayPtr BasicReaderWriter::ReadDataAsync( 
	_In_ BasicReaderWriter::StringArg FileName 
	)
{
#ifndef REIGN_DESKTOP // Metro or Phone app should use this implementation
	// Here we should acquire the storage file 
    return CreateAsync( m_Folder->GetFileAsync(FileName) ).then( [=]( Storage::StorageFile^ File )
    {
        return File->OpenReadAsync(); // Open desired file
    }).then( [=]( Storage::Streams::IRandomAccessStreamWithContentType^ Stream )
    {
		// Open the streamed data reader to access the data stored in file 
        Storage::Streams::DataReader^ Reader = ref new Storage::Streams::DataReader(Stream);
		// Get number of bytes we want to read from file and ...
        uint32 uiFileSize = static_cast<uint32>(Stream->Size); 
		// ... load all the data from file to buffer
        return CreateAsync( Reader->LoadAsync(uiFileSize) ).then( [=]( uint32 uiCount )
        {
            auto FileData = ref new Platform::Array<byte>(uiFileSize); // Alloc buffer 
            Reader->ReadBytes(FileData); // Read all data 
            return FileData; // Return the buffer 
        });
    });
#else // Just for compability for Desktop apps 
	return CreateAsync( [this, FileName]( Void )
	{
		return ReadData(FileName);
	});
#endif
}

UInt32 BasicReaderWriter::WriteData( 
	_In_ BasicReaderWriter::StringArg FileName, 
	_In_ BasicReaderWriter::ByteArrayConstPtr Data 
	)
{
	CREATEFILE2_EXTENDED_PARAMETERS lCreateFileParams = { 0 };
	lCreateFileParams.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS);
	lCreateFileParams.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
	lCreateFileParams.dwFileFlags = FILE_FLAG_SEQUENTIAL_SCAN;
	lCreateFileParams.dwSecurityQosFlags = SECURITY_ANONYMOUS;
	lCreateFileParams.lpSecurityAttributes = nullptr;
	lCreateFileParams.hTemplateFile = nullptr;
		
#ifdef REIGN_DESKTOP
	auto TempPathStr = m_LocationPath + FileName;
	auto Path = TempPathStr.data();
#else
	auto Path = Platform::String::Concat(m_Path, FileName)->Data();
#endif

	WRL::Wrappers::FileHandle lFile(
		CreateFile2(
			Path,
			GENERIC_WRITE, // !
			FILE_SHARE_READ,
			CREATE_ALWAYS, // !
			&lCreateFileParams
			)
		);

	if (lFile.Get() == INVALID_HANDLE_VALUE)
	{
#ifdef REIGN_DESKTOP
		throw std::exception();
#else
		throw ref new Platform::FailureException();
#endif
	}

    DWORD dwNumBytesWritten;
    if ((!WriteFile( lFile.Get(), Data->Data, Data->Length, &dwNumBytesWritten, nullptr )) || 
		(dwNumBytesWritten != Data->Length))
    {
#ifdef REIGN_DESKTOP
		throw std::exception();
#else
		throw ref new Platform::FailureException();
#endif
    }

    return( dwNumBytesWritten );
}

BasicReaderWriter::AsyncUInt32 BasicReaderWriter::WriteDataAsync( 
	_In_ BasicReaderWriter::StringArg FileName, 
	_In_ BasicReaderWriter::ByteArrayConstPtr Data 
	)
{
#ifndef REIGN_DESKTOP // Metro or Phone app should use this implementation 
    return CreateAsync( m_Folder->CreateFileAsync(FileName, Storage::CreationCollisionOption::ReplaceExisting)
	).then([=]( Storage::StorageFile^ File) // Create the file we want to write the data into 
    {
		// Open created file with the desired access mode (write obviously). 
        return File->OpenAsync(Storage::FileAccessMode::ReadWrite);
    }).then([=]( Storage::Streams::IRandomAccessStream^ stream)
    {
        Storage::Streams::DataWriter^ Writer = ref new Storage::Streams::DataWriter(stream);
        Writer->WriteBytes(Data); // Open the stream writes instance and write all the data into the file 
		// Return the number of bytes we have commited to file after the stream writer is flushed. 
        return CreateAsync( Writer->StoreAsync() ).then( [=](uint32 uiBytes)
        {
            Writer->FlushAsync();
			return( uiBytes );
        });
    });
#else // Just for compability for Desktop apps 
	return CreateAsync( [this, FileName, Data]( Void )
	{
		return( WriteData(FileName, Data) );
	});
#endif
}